package jmine.tec.report.base;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import jmine.tec.report.api.Report;
import jmine.tec.report.api.batch.ReportCreator;
import jmine.tec.report.export.ReportFileExporter;
import jmine.tec.utils.Tuple;
import jmine.tec.utils.UnmodifiableTuple;
import bancosys.tec.persist.controller.PersistenceEnabledController;

/**
 * Implementação base de report creators
 * 
 * @author Rafael Volpato
 * @date Nov 19, 2010
 * @param <P> Parametro utilizado pelo report
 */
public abstract class AbstractReportCreator<P extends AbstractReportParameter> implements ReportCreator<P>, Serializable {

    public static final int CASAS_DECIMAIS_QTDE_COTAS = 8;

    public static final int CASAS_DECIMAIS_VALOR = 2;

    public static final int CASAS_DECIMAIS_INTEIRO = 0;

    public static final DateFormat DATE_FORMAT = new SimpleDateFormat("dd-MM-yyyy");

    private transient ReportFileExporter exporter;

    private transient PersistenceEnabledController controller;

    private Class<P> parameterClass;

    /**
     * Construtor
     * 
     * @param parameterClass parameterClass
     */
    public AbstractReportCreator(Class<P> parameterClass) {
        this.parameterClass = parameterClass;
    }

    /**
     * {@inheritDoc}
     */
    public List<Tuple<String, InputStream>> createReportFiles(P parameter) throws IOException {
        List<Tuple<String, InputStream>> result = new LinkedList<Tuple<String, InputStream>>();
        String fileName;
        for (Tuple<String, Report> report : this.doCreateReports(parameter)) {
            fileName = report.head() + this.exporter.getFileSuffix();
            result.add(new UnmodifiableTuple<String, InputStream>(fileName, this.getInputStream(report.tail())));
        }
        return result;
    }

    /**
     * {@inheritDoc}
     */
    public List<Report> createReports(P parameter) {
        List<Report> result = new LinkedList<Report>();
        for (Tuple<String, Report> report : this.doCreateReports(parameter)) {
            result.add(report.tail());
        }
        return result;
    }

    /**
     * Cria uma nova instância do parâmetro recebido pelo relatório.
     * 
     * @return parâmetro
     */
    public P createParameter() {
        try {
            Constructor<P> constructor = this.parameterClass.getDeclaredConstructor(new Class[0]);
            P parameter = constructor.newInstance(new Object[0]);
            parameter.setController(this.controller);
            return parameter;
        } catch (IllegalArgumentException e) {
            throw new IllegalStateException("Falha instanciando parâmetro de criação de relatório.", e);
        } catch (InstantiationException e) {
            throw new IllegalStateException("Falha instanciando parâmetro de criação de relatório.", e);
        } catch (IllegalAccessException e) {
            throw new IllegalStateException("Falha instanciando parâmetro de criação de relatório.", e);
        } catch (InvocationTargetException e) {
            throw new IllegalStateException("Falha instanciando parâmetro de criação de relatório.", e);
        } catch (SecurityException e) {
            throw new IllegalStateException("Falha instanciando parâmetro de criação de relatório.", e);
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException("Falha instanciando parâmetro de criação de relatório.", e);
        }
    }

    /**
     * Cria o arquivo referente ao report e devolve um inputstream capaz de ler o resultado.
     * 
     * @param report report
     * @return InputStream
     * @throws IOException IOException
     */
    private InputStream getInputStream(Report report) throws IOException {
        File tmp = File.createTempFile("centaurus", "report");
        OutputStream stream = new FileOutputStream(tmp);
        this.exporter.export(stream, report);
        stream.flush();
        stream.close();
        return new FileInputStream(tmp);
    }

    /**
     * Cria os relatórios apropriados dado o parâmetro
     * 
     * @param parameter parameter
     * @return lista de tuplas de nome do arquivo para o relatório
     */
    protected abstract List<Tuple<String, Report>> doCreateReports(P parameter);

    /**
     * Auxiliar para empacotar um único relatório com nome de arquivo padrão.
     * 
     * @param prefix prefix
     * @param parameter parameter
     * @param report report
     * @return resultado da exportação de um relatório
     */
    protected List<Tuple<String, Report>> pack(String prefix, P parameter, Report report) {
        Tuple<String, Report> result = new UnmodifiableTuple<String, Report>(this.getFileName(prefix, parameter), report);
        return Collections.singletonList(result);
    }

    /**
     * Cria o nome de arquivo adequado para o filtro
     * 
     * @param prefix prefix
     * @param parameter parameter
     * @return filename
     */
    protected String getFileName(String prefix, P parameter) {
        String data = parameter.getDataFim() == null ? "" : "_" + DATE_FORMAT.format(parameter.getDataFim());
        return String.format("%s%s%s", prefix, parameter.getReportFileNamePrefix(), data);
    }

    /**
     * @return the exporter
     */
    public ReportFileExporter getExporter() {
        return this.exporter;
    }

    /**
     * @param exporter the exporter to set
     */
    public void setExporter(ReportFileExporter exporter) {
        this.exporter = exporter;
    }

    /**
     * @return the controller
     */
    public PersistenceEnabledController getController() {
        return this.controller;
    }

    /**
     * @param controller the controller to set
     */
    public void setController(PersistenceEnabledController controller) {
        this.controller = controller;
    }

    /**
     * {@inheritDoc}
     */
    public int compareTo(ReportCreator<?> o) {
        return this.getCreatorKey().compareTo(o.getCreatorKey());
    }
}
